﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;

namespace OnCommon
{


    public class SocketTcpServer : IDisposable
    {
        Socket serverSocket;
        SocketAsyncEventArgs socketAsyncArg;
        int bufferSize = 1024;// >9
        int packetByteLen = 9;
        public event Action<SocketTcpData> OnCompleted;


        public event Action<SocketTcpFileData, bool> OnFileCompleted;

        public void Listen(int port, int backlog)
        {

            serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

            serverSocket.Bind(new IPEndPoint(IPAddress.Any, port));
            serverSocket.Listen(backlog);
            socketAsyncArg = new SocketAsyncEventArgs();
            socketAsyncArg.Completed += ArgIOCompleted;
            BeginAccept(socketAsyncArg);
        }

        //开始等待链接
        void BeginAccept(SocketAsyncEventArgs asyncArg)
        {
            asyncArg.AcceptSocket = null;
            if (!serverSocket.AcceptAsync(asyncArg))
            {
                ProcessAccept(serverSocket, asyncArg);
            }
        }

        //链接接入
        void ProcessAccept(object sender, SocketAsyncEventArgs e)
        {
            Socket receiveSocket = e.AcceptSocket;
            e.AcceptSocket = null;

            var receiveArg = GetSocketAsyncEventEx(receiveSocket);
            //receiveArg中数据扩展属性 可以单独使用一个class管理，在accept的时候
            //new一个该对象，绑定到e.UserToken上。这样为更新好的实现。

            if (!receiveSocket.ReceiveAsync(receiveArg))
            {
                ProcessReceive(receiveArg);
            }

            this.BeginAccept(e);
        }
        internal SocketAsyncEventArgsEx GetSocketAsyncEventEx(Socket receiveSocket)
        {
            var receiveArg = new OnCommon.SocketAsyncEventArgsEx();
            receiveArg.Completed += ArgIOCompleted;
            receiveArg.SetBuffer(new byte[bufferSize], 0, bufferSize);
            receiveArg.AcceptSocket = receiveSocket;
            return receiveArg;
        }


        //消息异步完成通知
        void ArgIOCompleted(object sender, SocketAsyncEventArgs e)
        {
            switch (e.LastOperation)
            {
                case SocketAsyncOperation.Receive:
                    this.ProcessReceive(e as SocketAsyncEventArgsEx);
                    break;
                case SocketAsyncOperation.Send:

                    break;
                case SocketAsyncOperation.Accept:
                    this.ProcessAccept(sender, e);
                    break;
                case SocketAsyncOperation.Connect:
                case SocketAsyncOperation.Disconnect:
                case SocketAsyncOperation.None:
                case SocketAsyncOperation.ReceiveFrom:
                case SocketAsyncOperation.ReceiveMessageFrom:
                case SocketAsyncOperation.SendPackets:
                case SocketAsyncOperation.SendTo:
                default:
                    break;
            }
        }

        //消息接收处理
        void ProcessReceive(SocketAsyncEventArgsEx receiveArg)
        {
            #region 基本判断
            if (receiveArg.BytesTransferred <= 0 || receiveArg.SocketError != SocketError.Success)
            {
                receiveArg.AcceptSocket = null;
                return;
            }
            #endregion

            try
            {
                NewMethod(receiveArg.Buffer, receiveArg);
            }
            catch (Exception ex)
            {
                LogServices.LogService.Instance.Write(ex);
                throw;
            }
        }

        private void NewMethod(byte[] buffer, SocketAsyncEventArgsEx receiveArg)
        {
            int dataOffset = 0;//当前接收的数据已使用的偏移值
            byte[] data = new byte[receiveArg.BytesTransferred];//当前接收的数据
            Array.Copy(buffer, receiveArg.Offset, data, 0, data.Length);

        label_Packet://处理包头数据
            #region 处理包头数据
            if (receiveArg.HasFirst || receiveArg.HasPacket)
            {
                if (receiveArg.HasFirst)
                {
                    receiveArg.PacketData = new byte[packetByteLen];
                    receiveArg.HasFirst = false;
                }

                if (receiveArg.PacketByteLen + (data.Length - dataOffset) < packetByteLen)
                {
                    //接到的数据 + 已到的数据 如果小于包头大小

                    Array.Copy(data, dataOffset, receiveArg.PacketData, receiveArg.PacketByteLen, data.Length - dataOffset);
                    receiveArg.PacketByteLen += (data.Length - dataOffset);
                    goto label_receive;//重新接收数据
                }
                else
                {
                    //当前接到的数据中既有包头数据，可能也有需要传输的数据

                    receiveArg.HasPacket = false;
                    Array.Copy(data, dataOffset, receiveArg.PacketData, receiveArg.PacketByteLen, this.packetByteLen - receiveArg.PacketByteLen);

                    #region 构建包头
                    Packet packet = new Packet();
                    packet.PacketType = (PacketType)(int.Parse(Encoding.UTF8.GetString(new byte[] { receiveArg.PacketData[0] })));

                    var packetLengthData = new byte[8];
                    Array.Copy(receiveArg.PacketData, 1, packetLengthData, 0, 8);
                    packet.Length = int.Parse(Encoding.UTF8.GetString(packetLengthData).Replace('\0', ' ').Trim());
                    receiveArg.Packet = packet;
                    if (receiveArg.Packet.PacketType != PacketType.File)
                    {
                        receiveArg.Data = new byte[packet.Length];
                    }
                    else
                    {
                        receiveArg.FileLenByte = new byte[8];
                    }
                    #endregion

                    //当前接到的数据中刚好够包数据
                    if ((this.packetByteLen - receiveArg.PacketByteLen) == data.Length)
                    {
                        goto label_receive;//重新接收数据                                                                                                                  
                    }
                    else
                    {
                        dataOffset += (this.packetByteLen - receiveArg.PacketByteLen);
                        //可能也有需要传输的数据，将当前剩余的数据丢到数据包中
                        goto label_CopyDataToData;
                    }
                }
            }
            #endregion

        label_CopyDataToData://将当前剩余的数据丢到数据包中
            var vaildDataLen = data.Length - dataOffset;
            if (receiveArg.Packet.PacketType == PacketType.File)
            {
                #region 解析文件传输中的文件名字长度
                //8byte 附加信息长度 + 附加信息 + file
                if (receiveArg.FileLenByteLen < 8)
                {
                    receiveArg.SocketTcpFileData = new SocketTcpFileData();
                    receiveArg.SocketTcpFileData.AccpetSocket = receiveArg.AcceptSocket;
                    receiveArg.SocketTcpFileData.IPEndPoint = receiveArg.AcceptSocket.RemoteEndPoint as IPEndPoint;
                    //当前数据不够,或者刚好 文件信息 第一部分
                    if (receiveArg.FileLenByteLen + vaildDataLen <= 8)
                    {
                        Array.Copy(data, dataOffset, receiveArg.FileLenByte, receiveArg.FileLenByteLen, vaildDataLen);
                        receiveArg.FileLenByteLen += vaildDataLen;

                        if (receiveArg.FileLenByteLen == 8)
                            receiveArg.FileNameLen = int.Parse(Encoding.UTF8.GetString(receiveArg.FileLenByte).Replace('\0', ' ').Trim());

                        goto label_receive;//重新接收数据
                    }
                    else
                    {
                        //当前数据有 文件信息第一部分，还有其他数据
                        var dL = 8 - receiveArg.FileLenByteLen;
                        Array.Copy(data, dataOffset, receiveArg.FileLenByte, receiveArg.FileLenByteLen, dL);
                        dataOffset += dL;

                        receiveArg.FileLenByteLen += dL;
                        receiveArg.FileNameLen = int.Parse(Encoding.UTF8.GetString(receiveArg.FileLenByte).Replace('\0', ' ').Trim());
                        receiveArg.FileNameByte = new byte[receiveArg.FileNameLen];

                        vaildDataLen = data.Length - dataOffset;

                        goto label_File_Name;
                    }
                }
                #endregion

            label_File_Name://文件附加信息处理
                #region 解析文件传输中的文件名
                if (receiveArg.FileNameByteLen < receiveArg.FileNameLen)
                {
                    //文件名已接收数据+当前有效数据 <= 文件名数据长度
                    if (receiveArg.FileNameByteLen + vaildDataLen <= receiveArg.FileNameLen)
                    {
                        Array.Copy(data, dataOffset, receiveArg.FileNameByte, receiveArg.FileNameByteLen, vaildDataLen);
                        receiveArg.FileNameByteLen += vaildDataLen;

                        if (receiveArg.FileNameByteLen == receiveArg.FileNameLen)
                            receiveArg.SocketTcpFileData.FileName = receiveArg.FileName = Encoding.UTF8.GetString(receiveArg.FileNameByte);

                        goto label_receive;//重新接收数据
                    }
                    else
                    {
                        //当前有效数据既有文件名，也有文件数据
                        var dL = receiveArg.FileNameLen - receiveArg.FileNameByteLen;
                        Array.Copy(data, dataOffset, receiveArg.FileNameByte, receiveArg.FileNameByteLen, dL);
                        dataOffset += dL;

                        receiveArg.FileNameByteLen += dL;
                        receiveArg.SocketTcpFileData.FileName = receiveArg.FileName = Encoding.UTF8.GetString(receiveArg.FileNameByte);

                        vaildDataLen = data.Length - dataOffset;

                        goto label_File_Content;
                    }
                }
                #endregion

            label_File_Content://文件正文，当前传输字节通知订阅者接收
                #region 解析文件传输中的文件数据
                //总的文件包长度-文件名长度-已接收文件长度 
                var fileLen = (receiveArg.Packet.Length - 8 - receiveArg.FileNameLen - receiveArg.ReceiveLen);
                //待接收长度 > 有效数据长度
                if (fileLen > vaildDataLen)
                {
                    var vaildData = new byte[vaildDataLen];
                    Array.Copy(data, dataOffset, vaildData, 0, vaildDataLen);

                    receiveArg.SocketTcpFileData.CurrentBytes = vaildData;
                    receiveArg.ReceiveLen += vaildDataLen;
                    receiveArg.SocketTcpFileData.CurrentFileLen = receiveArg.ReceiveLen;

                    this.OnFileCompleted(receiveArg.SocketTcpFileData, false);

                    if (receiveArg.SocketTcpFileData._HasFirst)
                        receiveArg.SocketTcpFileData._HasFirst = false;

                    goto label_receive;//重新接收数据
                }
                else
                {
                    //当前有效数据中，有下一个包
                    var vaildData = new byte[fileLen];
                    Array.Copy(data, dataOffset, vaildData, 0, fileLen);
                    dataOffset += fileLen;
                    receiveArg.ReceiveLen += fileLen;
                    receiveArg.SocketTcpFileData.CurrentBytes = vaildData;
                    receiveArg.SocketTcpFileData.CurrentFileLen = receiveArg.ReceiveLen;

                    this.OnFileCompleted(receiveArg.SocketTcpFileData, true);

                    if (receiveArg.SocketTcpFileData._HasFirst)
                        receiveArg.SocketTcpFileData._HasFirst = false;


                    receiveArg.ResetReceive();
                    //当前有效数据刚好用完
                    if (data.Length == dataOffset)
                        goto label_receive;//重新接收数据
                    else
                        goto label_Packet;//解析下一个包的数据
                }
                #endregion
            }//end 文件传输

            #region 非文件数据传输，处理传输数据
            //如果需要传输的数据长度 大于 已经接的数据长度+当前有效数据长度。则全copy。然后继续收数据
            if (receiveArg.Packet.Length > receiveArg.ReceiveLen + vaildDataLen)
            {
                Array.Copy(data, dataOffset, receiveArg.Data, receiveArg.ReceiveLen, vaildDataLen);
                receiveArg.ReceiveLen += vaildDataLen;
                goto label_receive;//重新接收数据
            }
            else if (receiveArg.Packet.Length == receiveArg.ReceiveLen + vaildDataLen)
            {
                //可用数据刚刚好。接收数据，触发完成事件。清空各个对象。
                Array.Copy(data, dataOffset, receiveArg.Data, receiveArg.ReceiveLen, vaildDataLen);
                goto label_OnCompleteEvent;
            }
            else
            {
                //可用数据前面部分是当前包的传输数据，后面部分是下一个包的数据。
                var dL = receiveArg.Packet.Length - receiveArg.ReceiveLen;
                Array.Copy(data, dataOffset, receiveArg.Data, receiveArg.ReceiveLen, dL);
                dataOffset += dL;
                receiveArg.HasPacket = true;
                goto label_OnCompleteEvent;
            }
            #endregion

        label_OnCompleteEvent://非文件传输完成，通知订阅者
            #region 非文件传输完成，通知订阅者
            if (this.OnCompleted != null)
            {
                SocketTcpData sd = new SocketTcpData();
                sd.IPEndPoint = receiveArg.AcceptSocket.RemoteEndPoint as IPEndPoint;
                sd.DataType = receiveArg.Packet.PacketType;
                sd.ByteData = receiveArg.Data;
                sd.AccpetSocket = receiveArg.AcceptSocket;
                this.OnCompleted(sd);
            }
            if (receiveArg.HasPacket)
            {
                receiveArg.ResetReceive();
                goto label_Packet;//解析下一个包的数据
            }
            receiveArg.ResetReceive();
            #endregion

        label_receive://继续接收消息
            #region 继续接收消息
            if (!receiveArg.AcceptSocket.ReceiveAsync(receiveArg))
            {
                this.ProcessReceive(receiveArg);
            }
            #endregion
        }

        public void Dispose()
        {
            if (serverSocket != null)
                serverSocket.Dispose();
            if (socketAsyncArg != null)
                socketAsyncArg.Dispose();
        }
    }
}
